/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.classdiagram.editpart;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.LayoutManager;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.uml2.uml.AggregationKind;
import org.eclipse.uml2.uml.Association;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ElementImport;
import org.eclipse.uml2.uml.Property;

import com.cea.papyrus.core.editor.ModelManager;
import com.cea.papyrus.core.editpart.GraphEdgeEditPart;
import com.cea.papyrus.core.editpart.LinkNameDisplayEditPart;
import com.cea.papyrus.core.editpart.RelationEndDisplayEditPart;
import com.cea.papyrus.core.editpart.RelationEndLabelDisplayEditPart;
import com.cea.papyrus.core.editpart.RelationEndMultiplicityDisplayEditPart;
import com.cea.papyrus.core.editpart.StereotypeDisplayEditPart;
import com.cea.papyrus.core.figures.MiddleEndpointLocator;
import com.cea.papyrus.core.figures.RelationEndpointLocator;
import com.cea.papyrus.core.ui.properties.advanced.EObjectPropertySource;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagram.classdiagram.figures.AssociationFigure;
import com.cea.papyrus.diagraminterchange2.di2.DiagramElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * This the class control of the association link.
 * 
 * @author Patrick Tessier
 */
public class AssociationEditPart extends GraphEdgeEditPart implements PropertyChangeListener {

	/**
	 * 
	 */
	protected IPropertySource propertySource = null;

	/**
	 * 
	 */
	protected LinkNameDisplayEditPart theNameLabel; //the name label of the association 

	/**
	 * 
	 */
	protected RelationEndDisplayEditPart targetRoleLabel;// the label for the role

	/**
	 * 
	 */
	protected RelationEndDisplayEditPart sourceRoleLabel; // the label for the second role

	/**
	 * 
	 */
	protected RelationEndDisplayEditPart targetMultiplicityLabel; // the label to display the multiplicity

	/**
	 * 
	 */
	protected RelationEndDisplayEditPart sourceMultiplicityLabel; // the laebl to display the second multiplicity

	/**
	 * 
	 */
	protected StereotypeDisplayEditPart theStereotypeLabel; 


	/**
	 * Creates a new AssociationEditPart.
	 * 
	 * @param graphEdge 
	 * @param modelManager 
	 */
	public AssociationEditPart(GraphEdge graphEdge,ModelManager modelManager) {
		super(graphEdge,modelManager);
		//initialization of all control dor each label
		theNameLabel =new LinkNameDisplayEditPart((GraphNode)getGraphEdge().getContained().get(0),this.getUmlElement(),modelManager);
		targetRoleLabel =new RelationEndLabelDisplayEditPart((GraphNode)getGraphEdge().getContained().get(1), this.getUmlElement(),true, modelManager);
		sourceRoleLabel =new RelationEndLabelDisplayEditPart((GraphNode)getGraphEdge().getContained().get(2), this.getUmlElement(), false, modelManager);
		targetMultiplicityLabel =new RelationEndMultiplicityDisplayEditPart((GraphNode)getGraphEdge().getContained().get(3), this.getUmlElement(), true, modelManager);
		sourceMultiplicityLabel =new RelationEndMultiplicityDisplayEditPart((GraphNode)getGraphEdge().getContained().get(4), this.getUmlElement(), false, modelManager);
		theStereotypeLabel=new StereotypeDisplayEditPart((GraphNode)getGraphEdge().getContained().get(5), this.getUmlElement(), modelManager, this);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gef.editparts.AbstractEditPart#createEditPolicies()
	 */
	//@Override
	/**
	 * 
	 */
	protected void createEditPolicies() {
		super.createEditPolicies();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gef.editparts.AbstractEditPart#getCommand(org.eclipse.gef.Request)
	 */
	//@Override
	/**
	 * 
	 * 
	 * @param request 
	 * 
	 * @return 
	 */
	public Command getCommand(Request request) {
		// command should be transmetted to contained parts
		CompoundCommand command = new CompoundCommand();
		command.add(theNameLabel.getCommand(request));
		command.add(targetRoleLabel.getCommand(request));
		command.add(sourceRoleLabel.getCommand(request));
		command.add(targetMultiplicityLabel.getCommand(request));
		command.add(sourceMultiplicityLabel.getCommand(request));
		command.add(theStereotypeLabel.getCommand(request));
		command.add(super.getCommand(request));
		return command;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.editpart.GraphEdgeEditPart#propertyChange(java.beans.PropertyChangeEvent)
	 */
	//@Override
	/**
	 * 
	 * 
	 * @param event 
	 */
	public void propertyChange(PropertyChangeEvent event) {
		//refreshVisuals();
		refreshBendpoints();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#createFigure()
	 */
	//@Override
	/**
	 * 
	 * 
	 * @return 
	 */
	protected IFigure createFigure() {
		return new AssociationFigure(
				AssociationFigure.nothing,
				AssociationFigure.nothing);
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.editpart.GraphEdgeEditPart#getUmlElement()
	 */
	//@Override
	/**
	 * 
	 * 
	 * @return 
	 */
	public Association getUmlElement() {
		return (Association)((Uml1SemanticModelBridge)(getGraphEdge()).getSemanticModel()).getElement();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gef.editparts.AbstractEditPart#getModelChildren()
	 */
	//@Override
	/**
	 * 
	 * 
	 * @return 
	 */
	protected List<DiagramElement> getModelChildren() { //look for all graphnodes, 
		ArrayList<DiagramElement> contained= new ArrayList<DiagramElement>();//that is associated to label
		Iterator<DiagramElement> it=getGraphEdge().getContained().iterator();
		while (it.hasNext()){
			GraphElement ge=(GraphElement)it.next();
			if (!(ge instanceof GraphEdge)){
				contained.add(ge);
			}
		}
		return contained;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gef.editparts.AbstractEditPart#createChild(java.lang.Object)
	 */
	//@Override
	/**
	 * 
	 * 
	 * @param model 
	 * 
	 * @return 
	 */
	protected EditPart createChild(Object model){
		if( model instanceof GraphNode) {
			if( ((GraphNode)model) == ((GraphNode)getGraphEdge().getContained().get(0)) ) {
				return theNameLabel;
			} else if( ((GraphNode)model) == ((GraphNode)getGraphEdge().getContained().get(1)) ) {
				return targetRoleLabel;
			} else if( ((GraphNode)model) == ((GraphNode)getGraphEdge().getContained().get(2)) ) {
				return sourceRoleLabel;
			} else if( ((GraphNode)model) == ((GraphNode)getGraphEdge().getContained().get(3)) ) {
				return targetMultiplicityLabel;
			} else if( ((GraphNode)model) == ((GraphNode)getGraphEdge().getContained().get(4)) ) {
				return sourceMultiplicityLabel;
			} else if( ((GraphNode)model) == ((GraphNode)getGraphEdge().getContained().get(5)) ) {
				return theStereotypeLabel;
			} else {
				return null;
				//throw new RuntimeException("unknown model list");
			}
		}
		return super.createChild(model);
	}

	/**
	 * Upon activation, attach to the model element as a property change listener.
	 */
	//@Override
	public void activate() {
		if (!isActive()) {

			super.activate();

			//add listener on associationEnd
			if (getUmlElement().getMemberEnds().get(0)!=null){
				((Notifier)getUmlElement().getMemberEnds().get(0)).eAdapters().add(_adapter);

				//cardinalities
				if (((Property)getUmlElement().getMemberEnds().get(0)).getLowerValue()!=null){
					((Notifier)((Property)getUmlElement().getMemberEnds().get(0)).getLowerValue()).eAdapters().add(_adapter);}	
				if (((Property)getUmlElement().getMemberEnds().get(0)).getUpperValue()!=null){
					((Notifier)((Property)getUmlElement().getMemberEnds().get(0)).getUpperValue()).eAdapters().add(_adapter);}	

			}

			if (getUmlElement().getMemberEnds().get(1)!=null){
				((Notifier)getUmlElement().getMemberEnds().get(1)).eAdapters().add(_adapter);
//				cardinalities
				if (((Property)getUmlElement().getMemberEnds().get(1)).getLowerValue()!=null){
					((Notifier)((Property)getUmlElement().getMemberEnds().get(1)).getLowerValue()).eAdapters().add(_adapter);}	
				if (((Property)getUmlElement().getMemberEnds().get(1)).getUpperValue()!=null){
					((Notifier)((Property)getUmlElement().getMemberEnds().get(1)).getUpperValue()).eAdapters().add(_adapter);}	

			}

		}
	}

	/**
	 * Upon deactivation, detach from the model element as a property change listener.
	 */
	//@Override
	public void deactivate() {
		if (isActive()) {
			if (getUmlElement().getMemberEnds().size()>0 && getUmlElement().getMemberEnds().get(0)!=null){
				((Notifier)getUmlElement().getMemberEnds().get(0)).eAdapters().remove(_adapter);
//				cardinalities
				if(((Notifier)((Property)getUmlElement().getMemberEnds().get(0)).getLowerValue()!=null)){
					((Notifier)((Property)getUmlElement().getMemberEnds().get(0)).getLowerValue()).eAdapters().remove(_adapter);
				}
				if(((Notifier)((Property)getUmlElement().getMemberEnds().get(0)).getUpperValue()!=null)){
					((Notifier)((Property)getUmlElement().getMemberEnds().get(0)).getUpperValue()).eAdapters().remove(_adapter);
				}

			}

			if (getUmlElement().getMemberEnds().size()>1 && getUmlElement().getMemberEnds().get(1)!=null){
				((Notifier)getUmlElement().getMemberEnds().get(1)).eAdapters().remove(_adapter);
				if(((Notifier)((Property)getUmlElement().getMemberEnds().get(1)).getLowerValue()!=null)){
					((Notifier)((Property)getUmlElement().getMemberEnds().get(1)).getLowerValue()).eAdapters().remove(_adapter);	}
				if(((Notifier)((Property)getUmlElement().getMemberEnds().get(1)).getUpperValue()!=null)){
					((Notifier)((Property)getUmlElement().getMemberEnds().get(1)).getUpperValue()).eAdapters().remove(_adapter);	
				}}
			super.deactivate();
		}

	}

	/**
	 * Updates figure ends (arrow and/or diamonds)
	 */
	private void setFigureNavigation() {
		int source=AssociationFigure.nothing; //to display arrow the association
		int target=AssociationFigure.nothing; // to display arrow the association

		//in the case where anchors does not exist
		if((getGraphEdge().getAnchorSource()==null)||(getGraphEdge().getAnchorTarget()==null)) {
			return;
		}

		// cast source into classifiers
		org.eclipse.uml2.uml.Classifier sourceClass = (org.eclipse.uml2.uml.Classifier)((Uml1SemanticModelBridge)getGraphEdge().getAnchorSource().getSemanticModel()).getElement();

		// get target element (element or imported element if the association is added to the element import)
		org.eclipse.uml2.uml.Classifier targetClass = null;
		Element targetElement=((Uml1SemanticModelBridge)getGraphEdge().getAnchorTarget().getSemanticModel()).getElement();
		if (targetElement instanceof ElementImport){
			targetClass=(Classifier)((ElementImport)targetElement).getImportedElement();
		} else {
			targetClass=(Classifier)targetElement;
		}

		// different algorithm if the target and the source classifiers are not the same and if they are equal
		if (targetClass!=sourceClass){
			// case target different than source class
			Iterator<Property> it =getUmlElement().getMemberEnds().iterator();
			while (it.hasNext()){
				Property p=(Property)it.next();

				// case: the property is a property of source Class
				if (sourceClass.getAttributes().contains(p)){
					if(target==AssociationFigure.nothing){
						target=AssociationFigure.navigable;}  // this is navigable association so put an arrow
					if (p.getAggregation().equals(AggregationKind.COMPOSITE_LITERAL)){
						source=AssociationFigure.composition;// this a composition
					}
					if (p.getAggregation().equals(AggregationKind.SHARED_LITERAL)){
						source=AssociationFigure.aggregation;//this aggregation
					}
				}
				// case: the property is a property of target class
				if (targetClass.getAttributes().contains(p)){
					if(source==AssociationFigure.nothing){
						source=AssociationFigure.navigable;} // the association is navigable

					if (p.getAggregation().equals(AggregationKind.COMPOSITE_LITERAL)){
						target=AssociationFigure.composition; //composition
					}
					if (p.getAggregation().equals(AggregationKind.SHARED_LITERAL)){
						target=AssociationFigure.aggregation;//aggregation
					}
				}
			}
		} else { 
			//the source and the target of the association is the same
			// Property p;
			Property p = getUmlElement().getMemberEnds().get(0);

			// if property does not belongs to the association, it is a navigable property (~target = navigable)
			if(!getUmlElement().getOwnedEnds().contains(p)) {
				if(target==AssociationFigure.nothing){
					target=AssociationFigure.navigable;
				}
				// sets the aggregation kind
				switch(p.getAggregation()) {
				case COMPOSITE_LITERAL:
					source = AssociationFigure.composition;
					break;
				case SHARED_LITERAL:
					source = AssociationFigure.aggregation;
				default:
					break;
				}
			}
			
			// same, but for second property (which corresponds to the source of the association)
			p = getUmlElement().getMemberEnds().get(1);
			if(!getUmlElement().getOwnedEnds().contains(p)) {
				if(source==AssociationFigure.nothing){
					source=AssociationFigure.navigable;
				}
				// sets the aggregation kind
				switch(p.getAggregation()) {
				case COMPOSITE_LITERAL:
					target = AssociationFigure.composition;
					break;
				case SHARED_LITERAL:
					target = AssociationFigure.aggregation;
				default:
					break;
				}
			}


//			if(!getUmlElement().getOwnedEnds().contains(p)){
//				target=AssociationFigure.navigable;
//				p=(Property)getUmlElement().getMemberEnds().get(0);
//				if (p.getAggregation().equals(AggregationKind.COMPOSITE_LITERAL)){
//					source=AssociationFigure.composition;
//				}
//				if (p.getAggregation().equals(AggregationKind.SHARED_LITERAL)){
//					source=AssociationFigure.aggregation;
//				}
//			}
//			if(!getUmlElement().getOwnedEnds().contains(getUmlElement().getMemberEnds().get(1))){
//				source=AssociationFigure.navigable;
//				p=(Property)getUmlElement().getMemberEnds().get(1);
//				if (p.getAggregation().equals(AggregationKind.COMPOSITE_LITERAL)){
//					target=AssociationFigure.composition;
//				}
//				if (p.getAggregation().equals(AggregationKind.SHARED_LITERAL)){
//					target=AssociationFigure.aggregation;
//				}
//			}
		}
		((AssociationFigure)getFigure()).setEnd(source,target);

	}

	/**
	 * {@inheritDoc}
	 */
	public Object getAdapter(Class adapter) {
		/* override the default behavior defined in AbstractEditPart
		 * which would expect the model to be a property sourced. 
		 * instead the editpart can provide a property source
		 */
		if (IPropertySource.class == adapter) {
			return getPropertySource();
		}
		return super.getAdapter(adapter);
	}

	/**
	 * {@inheritDoc}
	 */
	protected void refreshVisuals() {
		super.refreshVisuals();
		//figure=null;
		setFigureNavigation();
		Debug.debug(this,"refreshVisual", channel);
		theNameLabel.refresh();
		targetRoleLabel.refresh(); 
		sourceRoleLabel.refresh();
		targetMultiplicityLabel.refresh();
		sourceMultiplicityLabel.refresh();
		theStereotypeLabel.refresh();
		//calculate the middle point of the label
		org.eclipse.draw2d.geometry.Point middleLabel =new org.eclipse.draw2d.geometry.Point(
				theNameLabel.getGraphNode().getPosition().x-((Label) theNameLabel.getFigure()).getPreferredSize().width/2,
				theNameLabel.getGraphNode().getPosition().y-((Label) theNameLabel.getFigure()).getPreferredSize().height/2
		);

		// calculate the middle point of the link
		MiddleEndpointLocator lo =new MiddleEndpointLocator ((AssociationFigure)this.getFigure(),
				middleLabel);

		// display the name label
		IFigure f=theNameLabel.getFigure();
		f.setLocation(theNameLabel.getGraphNode().getPosition());


		if (theNameLabel.isVisible()){	
			this.getContentPane().getLayoutManager().setConstraint(theNameLabel.getFigure(),lo);
		}

		//		the stereotype label
		middleLabel =new org.eclipse.draw2d.geometry.Point(
				theStereotypeLabel.getGraphNode().getPosition().x-((Label) theStereotypeLabel.getFigure()).getPreferredSize().width/2,
				theStereotypeLabel.getGraphNode().getPosition().y-((Label) theStereotypeLabel.getFigure()).getPreferredSize().height/2
		);
		// calculate middle point of the link
		lo =new MiddleEndpointLocator ((AssociationFigure)this.getFigure(),
				middleLabel);

		f=theStereotypeLabel.getFigure();
		f.setLocation(theStereotypeLabel.getGraphNode().getPosition()); // set label at the good position

		if (theStereotypeLabel.isVisible()){
			this.getContentPane().getLayoutManager().setConstraint(theStereotypeLabel.getFigure(),lo);
		}
		//this.getContentPane().getLayoutManager().remove(theNameLabel.getFigure());}

		//display roles
		if (targetRoleLabel.isVisible()) {
			addLabel(this.getContentPane().getLayoutManager(), targetRoleLabel);
		}

		if (sourceRoleLabel.isVisible()) {
			addLabel(this.getContentPane().getLayoutManager(), sourceRoleLabel);
		}

		//display multiplicity
		if (targetMultiplicityLabel.isVisible()) {
			addLabel(this.getContentPane().getLayoutManager(), targetMultiplicityLabel);
		}

		if (sourceMultiplicityLabel.isVisible()) { 
			addLabel(this.getContentPane().getLayoutManager(), sourceMultiplicityLabel);
		}
	}

	/**
	 * to display a label at the end of the link.
	 * @param layoutManager of the current display
	 * @param theStringEditPart of the label
	 */
	protected void addLabel(LayoutManager layoutManager,RelationEndDisplayEditPart theStringEditPart){

		layoutManager.setConstraint(theStringEditPart.getFigure(),new RelationEndpointLocator(
				(AssociationFigure)this.getFigure(),
				theStringEditPart.isAtEnd(),
				theStringEditPart.getGraphNode().getPosition())); 
	}

	/**
	 * {@inheritDoc}
	 */
	protected IPropertySource getPropertySource() {
		if( propertySource == null ) {
			propertySource = new EObjectPropertySource( getUmlElement());
		}
		return propertySource;
	}

}